#region License
// Copyright (c) 2007 James Newton-King
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
#endregion

using System;
using System.Reflection;
using System.Linq.Expressions;
using Newtonsoft.Utilities.Miscellaneous;

namespace Newtonsoft.Utilities.Reflection
{
	public static class Reflector
	{
    public static ConstructorInfo GetConstructor<T>(Expression<Func<T>> constructorExpression)
    {
      ValidationUtils.ArgumentNotNull(constructorExpression, "constructorExpression");

      LambdaExpression lambda = constructorExpression as LambdaExpression;
      NewExpression invocation = lambda.Body as NewExpression;

      return invocation.Constructor;
    }

    public static EventInfo GetEvent<T>(Expression<Action<T>> eventExpression)
    {
      ValidationUtils.ArgumentNotNull(eventExpression, "eventExpression");

      LambdaExpression lambda = eventExpression as LambdaExpression;
      InvocationExpression invocation = lambda.Body as InvocationExpression;
      MemberExpression memberExpr = invocation.Expression as MemberExpression;

      EventInfo eventInfo = memberExpr.Member as EventInfo;
      if (eventInfo != null)
        return eventInfo;

      // hack. for some reason member is a FieldInfo. resolve to EventInfo
      MemberInfo member = memberExpr.Member.DeclaringType.GetMember(memberExpr.Member.Name)[0];
      eventInfo = member as EventInfo;
      if (eventInfo != null)
        return eventInfo;

      throw new ArgumentException("Expression is not an event.", "method");
    }

		/// <summary>
		/// Gets the method represented by the lambda expression.
		/// </summary>
		/// <exception cref="ArgumentNullException">The <paramref name="method"/> is null.</exception>
		/// <exception cref="ArgumentException">The <paramref name="method"/> is not a lambda expression or it does not represent a method invocation.</exception>
		public static MethodInfo GetMethod<T>(Expression<Action<T>> methodExpression)
		{
			return GetMethodInfo(methodExpression);
		}

    private static MethodInfo GetMethodInfo(Expression methodExpression)
    {
      ValidationUtils.ArgumentNotNull(methodExpression, "methodExpression");

      LambdaExpression lambda = methodExpression as LambdaExpression;
      if (lambda == null) throw new ArgumentException("Not a lambda expression", "method");
      if (lambda.Body.NodeType != ExpressionType.Call) throw new ArgumentException("Not a method call", "method");

      return ((MethodCallExpression)lambda.Body).Method;
    }

    /// <summary>
		/// Gets the property represented by the lambda expression.
		/// </summary>
		/// <exception cref="ArgumentNullException">The <paramref name="method"/> is null.</exception>
		/// <exception cref="ArgumentException">The <paramref name="method"/> is not a lambda expression or it does not represent a property access.</exception>
		public static PropertyInfo GetProperty<T>(Expression<Func<T, object>> propertyExpression)
		{
      ValidationUtils.ArgumentNotNull(propertyExpression, "propertyExpression");

			PropertyInfo info = GetMemberInfo(propertyExpression) as PropertyInfo;
			if (info == null) throw new ArgumentException("Member is not a property");

			return info;
		}

		/// <summary>
		/// Gets the field represented by the lambda expression.
		/// </summary>
		/// <exception cref="ArgumentNullException">The <paramref name="method"/> is null.</exception>
		/// <exception cref="ArgumentException">The <paramref name="method"/> is not a lambda expression or it does not represent a field access.</exception>
		public static FieldInfo GetField<T>(Expression<Func<T, object>> field)
		{
			FieldInfo info = GetMemberInfo(field) as FieldInfo;
			if (info == null) throw new ArgumentException("Member is not a field");

			return info;
		}

		private static MemberInfo GetMemberInfo(Expression member)
		{
			if (member == null) throw new ArgumentNullException("member");

			LambdaExpression lambda = member as LambdaExpression;
			if (lambda == null) throw new ArgumentException("Not a lambda expression", "member");

			MemberExpression memberExpr = null;

			// The Func<TTarget, object> we use returns an object, so first statement can be either 
			// a cast (if the field/property does not return an object) or the direct member access.
			if (lambda.Body.NodeType == ExpressionType.Convert)
			{
				// The cast is an unary expression, where the operand is the 
				// actual member access expression.
				memberExpr = ((UnaryExpression)lambda.Body).Operand as MemberExpression;
			}
			else if (lambda.Body.NodeType == ExpressionType.MemberAccess)
			{
				memberExpr = lambda.Body as MemberExpression;
			}

			if (memberExpr == null) throw new ArgumentException("Not a member access", "member");

			return memberExpr.Member;
		}
	}
}